/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using DaveSexton.DocProject.Engine;
using DaveSexton.DocProject.Resources;
using System.Globalization;

namespace DaveSexton.DocProject
{
	public sealed class DocProjectFileGroup : DocProjectItemBase
	{
		#region Public Properties
		public override SystemPath Path
		{
			get
			{
				return path;
			}
		}

		public override bool Exists
		{
			get
			{
				if (files == null)
					return path.Exists;
				else
				{
					missingFiles.Clear();

					foreach (string file in files)
					{
						if (!File.Exists(file))
							missingFiles.Add(file);
					}

					// NOTE: Exists must only return true if all files exist
					return missingFiles.Count == 0;
				}
			}
		}

		public override bool AnyExists
		{
			get
			{
				if (files == null)
					return path.Exists;
				else
				{
					foreach (string file in files)
						if (File.Exists(file))
							return true;

					return false;
				}
			}
		}

		public ICollection<string> MissingFiles
		{
			get
			{
				return missingFiles.AsReadOnly();
			}
		}
		#endregion

		#region Private / Protected
		private readonly SystemPath path;
		private readonly List<string> files;
		private readonly List<string> missingFiles = new List<string>();
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="DocProjectFileGroup" /> class.
		/// </summary>
		/// <param name="path">Absolute or relative path from the DocProject's configured <see cref="BuildSettings.TargetDirectory" />, including the file name.</param>
		/// <param name="missingBuildTypeDependency">Indicates the <see cref="BuildType" /> that can be executed to replace the item when the item is missing.</param>
		/// <param name="updatedBuildTypeDependency">Indicates the <see cref="BuildType" /> that is executed when the item has changed.</param>
		public DocProjectFileGroup(SystemPath path, bool removeOnClean, 
			MissingBuildTypeDependency missingBuildTypeDependency, UpdatedBuildTypeDependency updatedBuildTypeDependency, bool isRequiredOutput)
			: base(removeOnClean, false, missingBuildTypeDependency, updatedBuildTypeDependency, isRequiredOutput)
		{
			if (path == SystemPath.Empty)
				throw new ArgumentException(Resources.Errors.EmptySystemPathIsInvalid, "path");
			
			if (!path.HasWildcard && !path.IsDirectory)
				throw new ArgumentException(Resources.Errors.SystemPathNotDirectoryOrWildcard, "path");

			this.path = path;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="DocProjectFileGroup" /> class.
		/// </summary>
		/// <param name="path">Absolute or relative path from the DocProject's configured <see cref="BuildSettings.TargetDirectory" />, including the file name.</param>
		/// <param name="missingBuildTypeDependency">Indicates the <see cref="BuildType" /> that can be executed to replace the item when the item is missing.</param>
		/// <param name="updatedBuildTypeDependency">Indicates the <see cref="BuildType" /> that is executed when the item has changed.</param>
		public DocProjectFileGroup(SystemPath path, bool removeOnClean, 
			MissingBuildTypeDependency missingBuildTypeDependency, UpdatedBuildTypeDependency updatedBuildTypeDependency)
			: base(removeOnClean, false, missingBuildTypeDependency, updatedBuildTypeDependency)
		{
			if (path == SystemPath.Empty)
				throw new ArgumentException(Resources.Errors.EmptySystemPathIsInvalid, "path");

			if (!path.HasWildcard && !path.IsDirectory)
				throw new ArgumentException(Resources.Errors.SystemPathNotDirectoryOrWildcard, "path");

			this.path = path;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="DocProjectFileGroup" /> class.
		/// </summary>
		/// <param name="path">Absolute or relative path from the DocProject's configured <see cref="BuildSettings.TargetDirectory" />, including the file name.</param>
		/// <param name="missingBuildTypeDependency">Indicates the <see cref="BuildType" /> that can be executed to replace the item when the item is missing.</param>
		/// <param name="updatedBuildTypeDependency">Indicates the <see cref="BuildType" /> that is executed when the item has changed.</param>
		public DocProjectFileGroup(SystemPath basePath, IEnumerable<string> relativePaths, bool removeOnClean,
			MissingBuildTypeDependency missingBuildTypeDependency, UpdatedBuildTypeDependency updatedBuildTypeDependency, bool isRequiredOutput)
			: base(removeOnClean, false, missingBuildTypeDependency, updatedBuildTypeDependency, isRequiredOutput)
		{
			if (basePath == SystemPath.Empty)
				throw new ArgumentException(Resources.Errors.EmptySystemPathIsInvalid, "basePath");

			if (relativePaths == null)
				throw new ArgumentNullException("relativePaths");

			path = basePath;
			files = new List<string>();

			foreach (string relativePath in relativePaths)
				files.Add(basePath.CombineWith(relativePath));

			if (files.Count == 0)
				throw new ArgumentException(Resources.Errors.EmptyCollection, "relativePaths");
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="DocProjectFileGroup" /> class.
		/// </summary>
		/// <param name="path">Absolute or relative path from the DocProject's configured <see cref="BuildSettings.TargetDirectory" />, including the file name.</param>
		/// <param name="missingBuildTypeDependency">Indicates the <see cref="BuildType" /> that can be executed to replace the item when the item is missing.</param>
		/// <param name="updatedBuildTypeDependency">Indicates the <see cref="BuildType" /> that is executed when the item has changed.</param>
		public DocProjectFileGroup(SystemPath basePath, IEnumerable<string> relativePaths, bool removeOnClean,
			MissingBuildTypeDependency missingBuildTypeDependency, UpdatedBuildTypeDependency updatedBuildTypeDependency)
			: base(removeOnClean, false, missingBuildTypeDependency, updatedBuildTypeDependency)
		{
			if (basePath == SystemPath.Empty)
				throw new ArgumentException(Resources.Errors.EmptySystemPathIsInvalid, "basePath");

			if (relativePaths == null)
				throw new ArgumentNullException("relativePaths");

			path = basePath;
			files = new List<string>();

			foreach (string relativePath in relativePaths)
				files.Add(basePath.CombineWith(relativePath));

			if (files.Count == 0)
				throw new ArgumentException(Resources.Errors.EmptyCollection, "relativePaths");
		}
		#endregion

		#region Methods
		public override bool Delete(IAnyProject project)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			bool notDeleted = true;

			if (files == null)
			{
				foreach (SystemPath file in path.GetFiles())
					if (Delete(project, file.Path))
						notDeleted = false;
			}
			else
			{
				foreach (string file in files)
					if (Delete(project, file))
						notDeleted = false;
			}

			return !notDeleted;
		}

		private static bool Delete(IAnyProject project, string file)
		{
			try
			{
				project.GetItem(file).Delete();
				return true;
			}
			catch (UnauthorizedAccessException ex)
			{
				Log.Exception(null, Errors.ProjectItemNotRemovedLogMessage, Environment.NewLine, file, ex.Message);
			}
			catch (PathTooLongException ex)
			{
				Log.Exception(null, Errors.ProjectItemNotRemovedLogMessage, Environment.NewLine, file, ex.Message);
			}
			catch (NotSupportedException ex)
			{
				Log.Exception(null, Errors.ProjectItemNotRemovedLogMessage, Environment.NewLine, file, ex.Message);
			}
			catch (DirectoryNotFoundException ex)
			{
				Log.Exception(null, Errors.ProjectItemNotRemovedLogMessage, Environment.NewLine, file, ex.Message);
			}
			catch (IOException ex)
			{
				Log.Exception(null, Errors.ProjectItemNotRemovedLogMessage, Environment.NewLine, file, ex.Message);
			}

			return false;
		}

		public override bool HasChangedSince(DateTime dateTime)
		{
			if (files == null)
				return path.HasChangedSince(dateTime);
			else
			{
				foreach (string file in files)
					if (System.IO.File.GetLastWriteTime(file) > dateTime)
						return true;

				return false;
			}
		}
		#endregion
	}
}
